import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;

/**
 * 
 * @author Jonathan Hsu, Seth Mielke
 * 
 * @modified 8/6/11
 * 
 */
public class OpBR extends Operation {

	public OpBR(Word[] register, Word[] memory, FileReader programIn,
			FileWriter programOut) {
		super(register, memory, programIn, programOut);
	}

	/**
	 * This operation executes an unconditional branch. When r=0 S is ignored
	 * and the program halts. When x=0 the machine does this quietly. When x=1
	 * the machine dumps all memory to display but does not reset it. When x=2
	 * the machine dumps all registers and PC to display. When x=3 it dumps both
	 * memory and registers, along with the PC to display. When r=1 the machine
	 * dumps all registers, PC, and memory to display and branches to address
	 * S(x). When r=2 the machine branches to address PC + S(x), and when r=3 it
	 * simply branches to address S(x).
	 * 
	 * 
	 * @param pc
	 *            is the program counter, which holds the location of the next
	 *            instruction to be executed.
	 * @param traceTable
	 *            is the table that holds the values of of registers and memory
	 *            before and after each instruction execution.
	 * @return integer value of the program counter which indicates the location
	 *         of instruction to execute next.
	 * @throws IOException
	 */
	public int exec(int pc, Map<String, Word> traceTable) throws IOException {
		// u1 and u2 values must be set to 0 for any operation to take place,
		// according to the machine's manual.
		if (memory[pc].u1() == 0 && memory[pc].u2() == 0) {
			int r = memory[pc].r();
			int sx = memory[pc].s() + memory[pc].x();
			int x = memory[pc].x();

			if (sx > 255 || sx < 0) {
				programOut.write('\n'
						+ "Out of Bounds error at memory location "
						+ String.format("%02x", pc) + '\n');
				return pc + 1;
			} else {
				traceTable.put("Before: Register #" + Integer.toString(r),
						register[r].copy());
				traceTable.put("Before: Memory Location "
						+ String.format("%02x", sx), memory[sx].copy());
				Word PC = new Word(pc);
				if (r == 0) {
					// Halt - means to terminate the fetch-execute cycle
					if (x == 0) {
						// quiet -no additional output is sent to the process
						// output
						// file
						// This is handled in the machine class run loop

					} else if (x == 1) {
						// dump (display, but neither reset nor erase) all
						// memory
						programOut.write('\n');
						for (int i = 0; i < 256; i++) {
							programOut.write("Memory at location " + i + " is "
									+ memory[i].toString() + '\n');
						}
					} else if (x == 2) {
						// dump all registers (and PC)
						programOut.write('\n');
						for (int i = 0; i < 4; i++) {
							programOut.write("Register at location " + i
									+ " is " + register[i].toString() + '\n');
						}
						programOut.write("Program counter is at location "
								+ String.format("%02x", pc) + '\n');
					} else {
						// dump both memory and registers (and PC)
						programOut.write('\n');
						for (int i = 0; i < 4; i++) {
							programOut.write("Register at location " + i
									+ " is " + register[i].toString() + '\n');
						}
						for (int i = 0; i < 256; i++) {
							programOut.write("Memory at location " + i + " is "
									+ memory[i].toString() + '\n');
						}
						programOut.write("Program counter is at location "
								+ String.format("%02x", pc) + '\n');
					}
					traceTable.put("After: Register #" + Integer.toString(r),
							register[r]);
					traceTable.put("After: Memory Location "
							+ String.format("%02x", sx), memory[sx]);
					// not sure where to return for R == 0
					return pc + 1;
				} else if (r == 1) {
					// dump all registers, PC, and memory
					programOut.write('\n');
					for (int i = 0; i < 4; i++) {
						programOut.write("Register at location " + i + " is "
								+ register[i].toString() + '\n');
					}
					for (int i = 0; i < 256; i++) {
						programOut.write("Memory at location " + i + " is "
								+ memory[i].toString() + '\n');
					}
					programOut.write("Program counter is at location "
							+ String.format("%02x", pc) + '\n');
					traceTable.put("After: Register #" + Integer.toString(r),
							register[r]);
					traceTable.put("After: Memory Location "
							+ String.format("%02x", sx), memory[sx]);
					return sx;
				} else if (r == 2) {
					programOut.write('\n');
					if (pc + sx > 255) {
						programOut.write('\n'
								+ "Out of Bounds error at memory location "
								+ String.format("%02x", pc) + '\n');
						traceTable.put("After: Register #"
								+ Integer.toString(r), register[r]);
						traceTable.put("After: Memory Location "
								+ String.format("%02x", sx), memory[sx]);
						return pc + 1;
					}
					traceTable.put("After: Register #" + Integer.toString(r),
							register[r]);
					traceTable.put("After: Memory Location "
							+ String.format("%02x", sx), memory[sx]);
					return pc + sx + 1;
				} else { // r = 3, branch to S(x)
					traceTable.put("After: Register #" + Integer.toString(r),
							register[r]);
					traceTable.put("After: Memory Location "
							+ String.format("%02x", sx), memory[sx]);
					return sx;
				}
			}
		} else {
			programOut.write('\n' + "U1 or U2 not valid at "
					+ String.format("%02x", pc).toUpperCase()
					+ "resulting in a no-op." + '\n');
			return pc + 1;
		}
	}
}
